By Dimitre Novatchev |
Language | XPATH |
Category | designpatterns, xml |
Posted | 28 Mar 2001 |
Updated | 01 Apr 2001 |
Summary | ||||
This snippet shows how to generate in a single XPath expression a string, which depends on some other boolean XPath expression. It may be easily generalised to generate a string depending on a condition, which has more than 2 outcomes. | ||||
I love XPath for its power to select a node-set based on complex conditions. | ||||
At the same time such capabilities for specifying a string as opposed to a nodeset, seem non-existant. How often have you had to use a verbose multi-line xsl:choose construct just to specify that "in case1 use string1, in case2 use string2, ..., in caseN use stringN"? | ||||
And what if you have decided not to use an XSLT transformation but only the DOM methods -- then you don't have even xsl:choose to use... | ||||
In all such cases we feel the need of a technique, which would allow us to specify in a single XPath expression a string, which depends on condition(s). | ||||
Here's how to do it: If you remember another of my snippets for advanced sorting of dynamically constructed strings | ||||
(http://www.vbxml.com/snippetcentral/main.asp?view=viewsnippet&id=v20010227100924), | ||||
there I used the concat() function with two operands. The two operands were specified in such a way, that for any different pair of values they'd have, one would be a nonempty string, and the other would be the empty string. | ||||
We'll use the same approach now. We want an XPath expression, which returns a string when some given condition is true, and returns the empty string if this same condition is false. | ||||
We can think of "true" as "1" and of "false" as "0". | ||||
Zero comes handy -- the empty string has a length of zero. | ||||
But how to fit "1" to any string? | ||||
And which string - handling function can we use? | ||||
OK, substring() seems quite convenient. And here's the trick: we can use substring() with only two arguments. | ||||
substring(str,nOffset) | ||||
will return the (remainder of the) string str starting at offset nOffset. | ||||
In particular: | ||||
substring(str,1) returns the whole string | ||||
substring(str, nVeryLargeNumber) will return the empty string, if nVeryLargeNumber is guaranteed to be greater than any possible string length. | ||||
Do we know such a number? Hmm... Infinity? | ||||
So, the expression we might use would be: | ||||
concat( substring(str1,exp(Condition)), substring(str2,exp(not(Condition)) ) | ||||
and we want exp(Condition) to be 1 if Condition is true, and exp(Condition) to be Infinity if Condition is false. | ||||
A last moment of thought and... Here it is: | ||||
We express exp(Condition) as: | ||||
1 div Condition | ||||
Because a boolean expression is first converted to a number (true -> 1, false -> 0), we get exactly: | ||||
exp(true) = 1 exp(false) = Infinity. | ||||
To summarise: The XPath expression returning Str1 if a condition Cond is true and returning Str2 if this same condition Cond is false -- this is: | ||||
concat( substring(Str1,1 div Cond), substring(Str2,1 div not(Cond)) ) | ||||
The technique I described can easily be generalised to specify an XPath expression, which generates one of given N strings depending on the value of another XPath expression, which has exactly N possible different values. | ||||
I leave this to you as an exercise. | ||||
To illustrate what we have done, here's a small example. I want to have a template, which generates the text: "My girlfriend" when it is passed a parameter "Mary" and to generate the text "Some other girl" if the value of the parameter is not "Mary". | ||||
Of course, no xsl:if -s or xsl:when -s are allowed. | ||||
Here's the code, and when applied to any xml source document, it generates: | ||||
|
||||
XML Source is N/A | ||||
XSL Stylesheet | ||||
XSL Stylesheet | ||||
XML/HTML Result: | ||||
Text Result: | ||||